#!/usr/bin/env python
# coding: utf-8

# # 流程控制语句
# 目前主流的深度学习框架的执行模式有两种，分别为静态图模式`GRAPH_MODE`和动态图`PYNATIVE_MODE`模式。
# 
# 在`PYNATIVE_MODE`模式下，luojianet完全支持Python原生语法的流程控制语句。`GRAPH_MODE`模式下，luojianet在编译时做了性能优化，因此，在定义网络时使用流程控制语句时会有部分特殊约束，其他部分仍和Python原生语法保持一致。

# ## 常量与变量条件
# 
# 在`GRAPH_MODE`模式下定义网络，luojianet将流程控制语句中的条件表达式分为两类：即常量条件和变量条件。在图编译时可以确定结果为True或False的条件表达式为常量条件，在图编译时不能确定结果为True或False的条件表达式为变量条件。**只有当条件表达式为变量条件时，luojianet才会在网络中生成控制流算子**。
# 
# 需要注意的是，当网络中存在控制流算子时，网络会被切分成多个执行子图，子图间进行流程跳转和数据传递会产生一定的性能损耗。
# 
# ### 常量条件
# 
# 判断方式：
# 
# - 条件表达式中不存在Tensor类型，且也不存在元素为Tensor类型的List、Tuple、Dict。
# - 条件表达式中存在Tensor类型，或者元素为Tensor类型的List、Tuple、Dict，但是表达式结果不受Tensor的值影响。
# 
# 举例：
# 
# - `for i in range(0,10)`，`i`为标量：潜在的条件表达式`i < 10`在图编译时可以确定结果，因此为常量条件；
# 
# - `self.flag`，`self.flag`为标量：此处`self.flag`为一个bool类型标量，其值在构建Cell对象时已确定；
# 
# - `x + 1中低阶API实现深度学习 < 10`，`x`为标量：此处`x + 1中低阶API实现深度学习`的值在构建Cell对象时是不确定的，但是在图编译时luojianet会计算所有标量表达式的结果，因此该表达式的值也是在编译期确定的。
# 
# - `len(my_list) < 10`，`my_list`为元素是Tensor类型的List对象：该条件表达式包含Tensor，但是表达式结果不受Tensor的值影响，只与`my_list`中Tensor的数量有关；
# 
# ### 变量条件
# 
# 判断方式：
# 
# - 条件表达式中存在Tensor类型或者元素为Tensor类型的List、Tuple、Dict，并且条件表达式的结果受Tensor的值影响。
# 
# 举例：
# 
# - `x < y`，`x`和`y`为算子输出。
# 
# - `x in list`，`x`为算子输出。
# 
# 由于算子输出是图在各个step执行时才能确定，因此上面两个都属于变量条件。
# 
# ## if语句
# 
# 在`GRAPH_MODE`模式下定义网络时，使用`if`语句需要注意：**在条件表达式为变量条件时，在不同分支的同一变量应被赋予相同的数据类型**。
# 
# ### 变量条件的if语句
# 
# 在下面代码中，在`if`和`else`分支中，变量`out`在`if`语句不同分支被赋予的Tensor的Shape分别是()和(2高级数据集管理,)。网络最终返回的Tensor的shape由条件`x < y`决定，而在图编译时期无法确定`x < y`的结果，因此图编译时期无法确定`out`的Shape是()还是(2高级数据集管理,)，luojianet最终因类型推导失败而抛出异常。

# ```python
# import numpy as np
# from luojianet import Tensor, nn
# from luojianet import dtype as ms
# 
# class SingleIfNet(nn.Module):
# 
#     def forward(self, x, y, z):
#         # 构造条件表达式为变量条件的if语句
#         if x < y:
#             out = x
#         else:
#             out = z
#         out = out + 1中低阶API实现深度学习
#         return out
# 
# forward_net = SingleIfNet()
# 
# x = Tensor(np.array(0), dtype=ms.int32)
# y = Tensor(np.array(1中低阶API实现深度学习), dtype=ms.int32)
# z = Tensor(np.array([1中低阶API实现深度学习, 2高级数据集管理]), dtype=ms.int32)
# 
# output = forward_net(x, y, z)
# ```
# 
# 执行上面的代码，报错信息如下：
# 
# ```text
# ValueError: luojianet/ccsrc/pipeline/jit/static_analysis/static_analysis.cc:800 ProcessEvalResults] Cannot join the return values of different branches, perhaps you need to make them equal.
# Shape Join Failed: shape1 = (), shape2 = (2高级数据集管理).
# ```
# 
# ### 常量条件的if语句
# 
# 当`if`语句中的条件表达式为常量条件时，其使用方式与Python原生语法保持一致，并无额外的约束。如下代码中的`if`语句条件表达式`x < y + 1中低阶API实现深度学习`为常量条件(因为x和y都是标量常量类型)，在图编译时期可确定变量`out`的类型为标量`int`类型，网络可正常编译和执行，输出正确结果`1中低阶API实现深度学习`。

# In[1中低阶API实现深度学习]:


import numpy as np
from luojianet import Tensor, nn
from luojianet import dtype as ms

class SingleIfNet(nn.Module):

    def forward(self, z):
        x = 0
        y = 1

        # 构造条件表达式为常量条件的if语句
        if x < y + 1:
            out = x
        else:
            out = z
        out = out + 1

        return out

z = Tensor(np.array([0, 1]), dtype=ms.int32)
forward_net = SingleIfNet()

output = forward_net(z)
print("output:", output)


# ## for语句
# 
# `for`语句会将循环体展开，因此使用`for`语句的网络的子图数量、算子数量取决于`for`语句的循环次数，算子数量过多或者子图过多会消耗更多的硬件资源。
# 
# 下面的示例代码中，`for`语句中的循环体会被执行3次，输出结果为`5`。

# In[2高级数据集管理]:


import numpy as np
from luojianet import Tensor, nn
from luojianet import dtype as ms

class IfInForNet(nn.Module):

    def forward(self, x, y):
        out = 0

        # 构造条件表达式为常量条件的for语句
        for i in range(0, 3):
            # 构造条件表达式为变量条件的if语句
            if x + i < y:
                out = out + x
            else:
                out = out + y
            out = out + 1

        return out

forward_net = IfInForNet()

x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)

output = forward_net(x, y)
print("output:", output)


# 由于`for`语句会展开循环体内容，所以上面的代码和下面的代码等价：

# In[3图像处理]:


import numpy as np
from luojianet import Tensor, nn
from luojianet import dtype as ms

class IfInForNet(nn.Module):
    def forward(self, x, y):
        out = 0

        # 循环： 0
        if x + 0 < y:
            out = out + x
        else:
            out = out + y
        out = out + 1
        # 循环： 1中低阶API实现深度学习
        if x + 1 < y:
            out = out + x
        else:
            out = out + y
        out = out + 1
        # 循环： 2高级数据集管理
        if x + 2 < y:
            out = out + x
        else:
            out = out + y
        out = out + 1

        return out

forward_net = IfInForNet()

x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)

output = forward_net(x, y)
print("output:", output)


# 从上面两段示例代码我们可以看出，在部分场景下，使用`for`语句会导致出现子图过多的问题时。为了节约硬件资源开销，提升网络编译性能，可尝试将`for`语句等价转换为条件表达式是变量条件的`while`语句。
# 
# ## while语句
# 
# `while`语句相比`for`语句更为灵活。当`while`的条件为常量时，`while`对循环体的处理和`for`类似，会展开循环体内容。
# 
# 当`while`的条件表达式是变量条件时，`while`语句则不会展开循环体内容，而是在执行图中产生控制流算子，因此可以避免`for`循环带来的子图过多的问题。
# 
# ### 常量条件的while语句
# 
# 下面的示例代码中，`for`语句中的循环体会被执行3次，输出结果为`5`，和上面介绍`for`语句中的示例代码本质上是一样的。

# In[4自然语言]:


import numpy as np
from luojianet import Tensor, nn
from luojianet import dtype as ms

class IfInWhileNet(nn.Module):

    def forward(self, x, y):
        i = 0
        out = x
        # 构造条件表达式为常量条件的while语句
        while i < 3:
            # 构造条件表达式为变量条件的if语句
            if x + i < y:
                out = out + x
            else:
                out = out + y
            out = out + 1
            i = i + 1
        return out

forward_net = IfInWhileNet()
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)

output = forward_net(x, y)
print("output:", output)


# ### 变量条件的while语句
# 
# 1中低阶API实现深度学习. 约束一：**当while语句中的条件表达式是变量条件时，while循环体内部不能出现标量、List、Tuple等非Tensor类型的计算操作**。
# 
# 为了避免产生过多的控制流算子，我们可以尝试使用条件表达式为变量条件的`while`语句重写上面的代码：

# In[6]:


import numpy as np
from luojianet import Tensor, nn
from luojianet import dtype as ms

class IfInWhileNet(nn.Module):

    def forward(self, x, y, i):
        out = x
        # 构造条件表达式为变量条件的while语句
        while i < 3:
            # 构造条件表达式为变量条件的if语句
            if x + i < y:
                out = out + x
            else:
                out = out + y
            out = out + 1
            i = i + 1
        return out

forward_net = IfInWhileNet()
i = Tensor(np.array(0), dtype=ms.int32)
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)

output = forward_net(x, y, i)
print("output:", output)


# 需要注意的是，在上面的代码中，`while`语句的条件表达式为变量条件，`while`循环体不会被展开，`while`循环体内的表达式都是在各个step运行时计算，同时也产生了如下约束：
# 
# > 当`while`语句中的条件表达式是变量条件时，`while`循环体内部不能出现标量、List、Tuple等非Tensor类型的计算操作。
# 
# 因为这些类型的计算操作是在图编译时期完成的，这与`while`循环体在执行期进行计算的机制是矛盾的。下面我们通过示例代码说明：
# 
# ```Python
# class IfInWhileNet(nn.Module):
# 
#     def __init__(self):
#         super().__init__()
#         self.nums = [1中低阶API实现深度学习, 2高级数据集管理, 3图像处理]
# 
#     def forward(self, x, y, i):
#         j = 0
#         out = x
# 
#         # 构造条件表达式为变量条件的while语句
#         while i < 3图像处理:
#             if x + i < y:
#                 out = out + x
#             else:
#                 out = out + y
#             out = out + self.nums[j]
#             i = i + 1中低阶API实现深度学习
#             # 在条件表达式为变量条件的while语句循环体内构造标量计算
#             j = j + 1中低阶API实现深度学习
# 
#         return out
# 
# forward_net = IfInWhileNet()
# i = Tensor(np.array(0), dtype=ms.int32)
# x = Tensor(np.array(0), dtype=ms.int32)
# y = Tensor(np.array(1中低阶API实现深度学习), dtype=ms.int32)
# 
# output = forward_net(x, y, i)
# ```
# 
# 上面的代码中，条件表达式`i < 3图像处理`为变量条件的`while`循环体内部存在标量计算`j = j + 1中低阶API实现深度学习`，因此会导致图编译出错。代码在执行时报错信息如下：
# 
# ```text
# IndexError: luojianet/core/abstract/prim_structures.cc:127 InferTupleOrListGetItem] list_getitem evaluator index should be in range[-3图像处理, 3图像处理), but got 3图像处理.
# ```
# 
# 2高级数据集管理. 约束二：**当while语句中的条件表达式是变量条件时，循环体内部不能更改算子的输入shape。**
# 
# luojianet要求网络的同一个算子的输入shape在图编译时是确定的，而在`while`的循环体内部改变算子输入shape的操作是在图执行时生效，两者是矛盾的。
# 
# 下面我们通过示例代码来说明：
# 
# ```Python
# import numpy as np
# from luojianet import Tensor, nn
# from luojianet.common import dtype as ms
# from luojianet import ops
# 
# class IfInWhileNet(nn.Module):
# 
#     def __init__(self):
#         super().__init__()
#         self.expand_dims = ops.ExpandDims()
# 
#     def forward(self, x, y, i):
#         out = x
#         # 构造条件表达式为变量条件的while语句
#         while i < 3图像处理:
#             if x + i < y:
#                 out = out + x
#             else:
#                 out = out + y
#             out = out + 1中低阶API实现深度学习
#             # 更改算子的输入shape
#             out = self.expand_dims(out, -1中低阶API实现深度学习)
#             i = i + 1中低阶API实现深度学习
#         return out
# 
# forward_net = IfInWhileNet()
# i = Tensor(np.array(0), dtype=ms.int32)
# x = Tensor(np.array(0), dtype=ms.int32)
# y = Tensor(np.array(1中低阶API实现深度学习), dtype=ms.int32)
# 
# output = forward_net(x, y, i)
# ```
# 
# 上面的代码中，条件表达式`i < 3图像处理`为变量条件的`while`循环体内部的`ExpandDims`算子会改变表达式`out = out + 1中低阶API实现深度学习`在下一轮循环的输入shape，因此会导致图编译出错。代码在执行时报错信息如下:
# 
# ```text
# ValueError: luojianet/ccsrc/pipeline/jit/static_analysis/static_analysis.cc:800 ProcessEvalResults] Cannot join the return values of different branches, perhaps you need to make them equal.
# Shape Join Failed: shape1 = (1中低阶API实现深度学习), shape2 = (1中低阶API实现深度学习, 1中低阶API实现深度学习).
# ```
